package com.shareppy.norman.core.sql;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.shareppy.norman.core.entity.AbstractEntity;
import com.shareppy.norman.core.sql.columns.Column;
import com.shareppy.norman.core.sql.conditions.Condition;
import com.shareppy.norman.core.sql.conditions.Eq;
import com.shareppy.norman.core.sql.conditions.Get;
import com.shareppy.norman.core.sql.conditions.Gt;
import com.shareppy.norman.core.sql.conditions.IsNotNull;
import com.shareppy.norman.core.sql.conditions.IsNull;
import com.shareppy.norman.core.sql.conditions.Let;
import com.shareppy.norman.core.sql.conditions.Lt;
import com.shareppy.norman.core.util.EntityUtils;
import com.shareppy.utils.CollectionsUtil;
import com.shareppy.utils.conversion.Converter;

public class QueryBuilder {

	private List<Column> columns;
	private List<Class<? extends AbstractEntity>> entities;
	private List<Condition> conditions;
	private List<String> groupBy;
	private String orderBy;
	private int limit;
	private int offset;

	private QueryBuilder() {
		super();
		columns = new ArrayList<Column>();
		entities = new ArrayList<Class<? extends AbstractEntity>>();
		conditions = new ArrayList<Condition>();
		groupBy = new ArrayList<String>();
		limit = -1;
		offset = 0;
	}

	public static QueryBuilder get() {
		return new QueryBuilder();
	}

	// Select
	public QueryBuilder select(Column... columns) {
		this.columns.addAll(Arrays.asList(columns));
		return this;
	}

	public QueryBuilder select(String... columns) {
		for (String column : columns) {
			this.columns.add(new Column(column));
		}
		return this;
	}

	// From
	public QueryBuilder from(Class<? extends AbstractEntity>... entities) {
		this.entities.addAll(Arrays.asList(entities));
		return this;
	}

	// Conditions
	// Equals
	public QueryBuilder equals(Condition condition) {
		this.conditions.add(condition);
		return this;
	}

	public QueryBuilder equalsTo(String left) {
		return equals(left, "?");
	}

	public QueryBuilder equals(String left, String right) {
		this.conditions.add(new Eq(left, right));
		return this;
	}

	// Greater than
	public QueryBuilder greaterThan(Gt greaterThan) {
		this.conditions.add(greaterThan);
		return this;
	}

	public QueryBuilder greaterThan(String left) {
		return greaterThan(left, "?");
	}

	public QueryBuilder greaterThan(String left, String right) {
		return greaterThan(new Gt(left, right));
	}

	// Greater or equals than
	public QueryBuilder greaterEqualsThan(Get get) {
		this.conditions.add(get);
		return this;
	}

	public QueryBuilder greaterEqualsThan(String left) {
		return greaterEqualsThan(left, "?");
	}

	public QueryBuilder greaterEqualsThan(String left, String right) {
		return greaterEqualsThan(new Get(left, right));
	}

	// Less than
	public QueryBuilder lessThan(Lt lessThan) {
		this.conditions.add(lessThan);
		return this;
	}

	public QueryBuilder lessThan(String left) {
		return lessThan(left, "?");
	}

	public QueryBuilder lessThan(String left, String right) {
		return lessThan(new Lt(left, right));
	}

	// Less or equals than
	public QueryBuilder lessEqualsThan(Let let) {
		this.conditions.add(let);
		return this;
	}

	public QueryBuilder lessEqualsThan(String left) {
		return lessEqualsThan(left, "?");
	}

	public QueryBuilder lessEqualsThan(String left, String right) {
		return lessEqualsThan(new Let(left, right));
	}

	// Is Null
	public QueryBuilder isNull(IsNull isNull) {
		this.conditions.add(isNull);
		return this;
	}

	public QueryBuilder isNull(String column) {
		return isNull(new IsNull(column));
	}

	// Is not Null
	public QueryBuilder isNotNull(IsNotNull isNotNull) {
		this.conditions.add(isNotNull);
		return this;
	}

	public QueryBuilder isNotNull(String column) {
		return isNotNull(new IsNotNull(column));
	}

	// Process

	@Override
	public String toString() {
		StringBuilder sql = new StringBuilder("SELECT ");
		if (this.columns.isEmpty()) {
			sql.append(CollectionsUtil.join(
					EntityUtils.getColumns(entities.get(0)), ','));
		} else {
			sql.append(CollectionsUtil.join(columns, ','));
		}
		sql.append(" FROM ")
				.append(CollectionsUtil
						.join(new Converter<List<Class<? extends AbstractEntity>>, List<String>>() {
							@Override
							public List<String> convert(
									List<Class<? extends AbstractEntity>> input) {
								List<String> output = new ArrayList<String>();
								for (Class<? extends AbstractEntity> entity : input) {
									output.add(EntityUtils.getTableName(entity));
								}
								return output;
							}
						}.convert(entities), ','));

		for (int i = 0; i < conditions.size(); i++) {
			if (i == 0) {
				sql.append(" WHERE ").append(conditions.get(i).toString());
			} else {
				sql.append(" AND ").append(conditions.get(i).toString());
			}
		}
		if (!groupBy.isEmpty()) {
			sql.append(" GROUP BY ").append(CollectionsUtil.join(groupBy, ','));
		}

		if (orderBy != null) {
			sql.append(" ORDER BY ").append(orderBy);
		}

		if (limit != -1) {
			sql.append(" LIMIT ").append(limit).append(" OFFSET ")
					.append(offset);
		}
		return sql.toString();
	}

	public QueryBuilder limit(int limit) {
		this.limit = limit;
		return this;
	}

	public QueryBuilder setOffset(int offset) {
		this.offset = offset;
		return this;
	}

	public QueryBuilder groupBy(String... groupBy) {
		for (String _groupBy : groupBy) {
			this.groupBy.add(_groupBy);
		}
		return this;
	}

	public QueryBuilder orderBy(String orderBy) {
		this.orderBy = orderBy;
		return this;
	}
	
	public QueryBuilder desc(String orderBy) {
		this.orderBy = orderBy.concat(" DESC ");
		return this;
	}
	
	public QueryBuilder asc(String orderBy) {
		this.orderBy = orderBy.concat(" ASC ");
		return this;
	}

}